package com.flatbuffer.message.converter;

import com.google.flatbuffers.Table;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 */
public class FlatBuffersHttpMessageConverter extends AbstractHttpMessageConverter<Table> {

    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private static final MediaType X_FLATBUFFERS = new MediaType("application", "x-fb", DEFAULT_CHARSET);


    private static final ConcurrentHashMap<Class<? extends Table>, Method> classMethodHashMap = new ConcurrentHashMap<Class<? extends Table>, Method>();

    /***
     *
     */
    public FlatBuffersHttpMessageConverter() {
        super(X_FLATBUFFERS);
    }

    /**
     * @param clazz
     * @return
     */
    @Override
    protected boolean supports(Class<?> clazz) {
        return Table.class.isAssignableFrom(clazz);
    }

    /**
     * @param table
     * @return
     */
    @Override
    protected MediaType getDefaultContentType(Table table) {
        return X_FLATBUFFERS;
    }

    /***
     *
     * @param clazz
     * @param inputMessage
     * @return
     * @throws IOException
     * @throws HttpMessageNotReadableException
     */
    @Override
    protected Table readInternal(Class<? extends Table> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        final int contentLength = (int) inputMessage.getHeaders().getContentLength();
        byte[] data = new byte[contentLength];
        inputMessage.getBody().read(data);
        Method method = getMethod(clazz);
        if (method != null) {
            try {
                return (Table) method.invoke(null, ByteBuffer.wrap(data));
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return null;
    }


    /**
     * @param clazz
     * @return
     */
    private Method getMethod(Class<? extends Table> clazz) {
        Method method = classMethodHashMap.get(clazz);
        if (method == null) {
            try {
                method = clazz.getMethod("getRootAs" + clazz.getSimpleName(), ByteBuffer.class);
                classMethodHashMap.put(clazz, method);// cache
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
        return method;
    }

    @Override
    protected void writeInternal(Table message, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        final byte[] dst = new byte[getContentLength(message, X_FLATBUFFERS).intValue()];
        message.getByteBuffer().get(dst);
        StreamUtils.copy(dst, outputMessage.getBody());
    }

    @Override
    protected Long getContentLength(Table table, MediaType contentType) throws IOException {
        final ByteBuffer buf = table.getByteBuffer();
        return (long) (buf.limit() - buf.position());
    }


}
